package io.wins.vertx.web;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

public class ErrorEntity {

    private final Code code;
    private final List<JsonObject> details;
    private JsonObject stackTrace;

    public ErrorEntity(final Code code, final List<JsonObject> details) {
        this.code = code;
        this.details = details;
    }

    public ErrorEntity withStackTrace(final Throwable throwable) {
        stackTrace = buildStackTraceJson(throwable);
        return this;
    }

    public Code code() {
        return code;
    }

    public JsonObject toJsonObject() {
        final JsonObject errorEntity = new JsonObject().put("code", code.toString()).put("errors", new JsonArray());
        details.forEach(detail -> errorEntity.getJsonArray("errors").add(detail));
        if (stackTrace != null) {
            errorEntity.put("stackTraces", stackTrace);
        }
        return errorEntity;
    }

    public static Builder builder(final Code code) {
        return new Builder(code);
    }

    private JsonObject buildStackTraceJson(final Throwable throwable) {
        final String stackTrace = getStackTrace(throwable);
        final String[] stacks = stackTrace.split(System.lineSeparator());
        final JsonObject stackJson = new JsonObject();
        final int lineNoLength = String.valueOf(stacks.length).length();
        for (int i = 0; i < stacks.length; i++) {
            final String lineNo = Strings.padStart(String.valueOf(i + 1), lineNoLength, "0".charAt(0));
            final String stackMessage = stacks[i].startsWith("\t") ? "    " + stacks[i].substring(1) : stacks[i];
            stackJson.put(lineNo, stackMessage);
        }
        return stackJson;
    }

    private String getStackTrace(final Throwable throwable) {
        final StringWriter sw = new StringWriter();
        final PrintWriter pw = new PrintWriter(sw, true);
        throwable.printStackTrace(pw);
        return sw.getBuffer().toString();
    }

    public static class Builder {

        private final Code code;
        private final List<JsonObject> details = new ArrayList<>();

        /**
         * @param code
         */
        Builder(final Code code) {
            Preconditions.checkNotNull(code, "code");
            this.code = code;
        }

        public Builder detail(final String message) {
            final JsonObject errorDetail = new JsonObject().put("message", message);
            details.add(errorDetail);
            return this;
        }

        public Builder detail(final String message, final Location location, final String name) {
            final JsonObject errorDetail = new JsonObject()
                    .put("message", message).put("location", location.toString()).put("name", name);
            details.add(errorDetail);
            return this;
        }
        public ErrorEntity build() {
            return new ErrorEntity(code, details);
        }

    }

    public enum Code {

        AUTHENTICATION_ERROR("authentication_error"),
        VALIDATION_ERROR("validation_error"),
        GENERAL_ERROR("general_error"),
        INTERNAL_ERROR("internal_error");

        private final String code;

        private Code(final String code) {
            this.code = code;
        }

        @Override
        public String toString() {
            return code;
        }
    }

    public enum Location {

        PATH("path"),
        HEADER("header"),
        BODY("body");

        private final String location;

        private Location(final String location) {
            this.location = location;
        }

        @Override
        public String toString() {
            return location;
        }
    }
}
